﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using Guog.Common.API;

namespace Guog.Common.Drawing
{
    /// <summary>
    /// Base class which represents a floating native window with transparency and custom painting
    /// </summary>
    public class FloatingWindow : NativeWindow, IDisposable
    {
        #region #  Enums  #
        public enum AnimateMode
        {
            Blend,
            SlideRightToLeft,
            SlideLeftToRight,
            SlideTopToBottom,
            SlideBottmToTop,
            RollRightToLeft,
            RollLeftToRight,
            RollTopToBottom,
            RollBottmToTop,
            ExpandCollapse
        }
        #endregion

        #region #  Fields  #
        private bool _disposed = false;
        private byte _alpha = 250;
        private Size _size = new Size(250, 50);
        private Point _location = new Point(50, 50);
        #endregion

        #region #  Methods  #

        #region == Painting ==
        /// <summary>
        /// Performs the painting of the window. Overide this method to provide custom painting
        /// </summary>
        /// <param name="e">A <see cref="PaintEventArgs"/> containing the event data.</param>
        protected virtual void PerformPaint(PaintEventArgs e)
        {
            using (LinearGradientBrush b = new LinearGradientBrush(this.Bound, Color.LightBlue, Color.DarkGoldenrod, 45f))
                e.Graphics.FillRectangle(b, this.Bound);
            e.Graphics.DrawString("Overide this PerformPaint method...", new Font(FontFamily.GenericSansSerif, 12f, FontStyle.Regular), new SolidBrush(Color.FromArgb(170, Color.Red)), new PointF(0f, 10f));
        }
        #endregion

        #region == Updating ==
        protected internal void Invalidate()
        {
            this.UpdateLayeredWindow();
        }
        private void UpdateLayeredWindow()
        {
            Bitmap bitmap1 = new Bitmap(this.Size.Width, this.Size.Height, PixelFormat.Format32bppArgb);
            using (Graphics graphics1 = Graphics.FromImage(bitmap1))
            {
                Rectangle rectangle1;
                SIZE size1;
                POINT point1;
                POINT point2;
                BLENDFUNCTION blendfunction1;
                rectangle1 = new Rectangle(0, 0, this.Size.Width, this.Size.Height);
                this.PerformPaint(new PaintEventArgs(graphics1, rectangle1));
                IntPtr ptr1 = User32.GetDC(IntPtr.Zero);
                IntPtr ptr2 = Gdi32.CreateCompatibleDC(ptr1);
                IntPtr ptr3 = bitmap1.GetHbitmap(Color.FromArgb(0));
                IntPtr ptr4 = Gdi32.SelectObject(ptr2, ptr3);
                size1.cx = this.Size.Width;
                size1.cy = this.Size.Height;
                point1.x = this.Location.X;
                point1.y = this.Location.Y;
                point2.x = 0;
                point2.y = 0;
                blendfunction1 = new BLENDFUNCTION();
                blendfunction1.BlendOp = 0;
                blendfunction1.BlendFlags = 0;
                blendfunction1.SourceConstantAlpha = this._alpha;
                blendfunction1.AlphaFormat = 1;
                User32.UpdateLayeredWindow(base.Handle, ptr1, ref point1, ref size1, ptr2, ref point2, 0, ref blendfunction1, 2); //2=ULW_ALPHA
                Gdi32.SelectObject(ptr2, ptr4);
                User32.ReleaseDC(IntPtr.Zero, ptr1);
                Gdi32.DeleteObject(ptr3);
                Gdi32.DeleteDC(ptr2);
            }
        }
        #endregion

        #region == Show / Hide ==
        /// <summary>
        /// Shows the window. Position determined by Location property in screen coordinates
        /// </summary>
        public virtual void Show()
        {
            if (base.Handle == IntPtr.Zero) //if handle don't equal to zero - window was created and just hided
                this.CreateWindowOnly();
            User32.ShowWindow(base.Handle, User32.SW_SHOWNOACTIVATE);
        }
        /// <summary>
        /// Shows the window.
        /// </summary>
        /// <param name="x">x-coordinate of window in screen coordinates</param>
        /// <param name="y">x-coordinate of window in screen coordinates</param>
        public virtual void Show(int x, int y)
        {
            this._location.X = x;
            this._location.Y = y;
            this.Show();
        }
        /// <summary>
        /// Shows the window with animation effect. Position determined by Location property in screen coordinates
        /// </summary>
        /// <param name="mode">Effect to be applied</param>
        /// <param name="time">Time, in milliseconds, for effect playing</param>
        public virtual void ShowAnimate(AnimateMode mode, uint time)
        {
            uint dwFlag = 0;
            switch (mode)
            {
                case AnimateMode.Blend:
                    dwFlag = User32.AW_BLEND;
                    break;
                case AnimateMode.ExpandCollapse:
                    dwFlag = User32.AW_CENTER;
                    break;
                case AnimateMode.SlideLeftToRight:
                    dwFlag = User32.AW_HOR_POSITIVE | User32.AW_SLIDE;
                    break;
                case AnimateMode.SlideRightToLeft:
                    dwFlag = User32.AW_HOR_NEGATIVE | User32.AW_SLIDE;
                    break;
                case AnimateMode.SlideTopToBottom:
                    dwFlag = User32.AW_VER_POSITIVE | User32.AW_SLIDE;
                    break;
                case AnimateMode.SlideBottmToTop:
                    dwFlag = User32.AW_VER_NEGATIVE | User32.AW_SLIDE;
                    break;
                case AnimateMode.RollLeftToRight:
                    dwFlag = User32.AW_HOR_POSITIVE;
                    break;
                case AnimateMode.RollRightToLeft:
                    dwFlag = User32.AW_HOR_NEGATIVE;
                    break;
                case AnimateMode.RollBottmToTop:
                    dwFlag = User32.AW_VER_NEGATIVE;
                    break;
                case AnimateMode.RollTopToBottom:
                    dwFlag = User32.AW_VER_POSITIVE;
                    break;
            }
            if (base.Handle == IntPtr.Zero)
                this.CreateWindowOnly();
            if ((dwFlag & User32.AW_BLEND) != 0)
                this.AnimateWithBlend(true, time);
            else
                User32.AnimateWindow(base.Handle, time, dwFlag);
        }
        /// <summary>
        /// Shows the window with animation effect.
        /// </summary>
        /// <param name="x">x-coordinate of window in screen coordinates</param>
        /// <param name="y">x-coordinate of window in screen coordinates</param>
        /// <param name="mode">Effect to be applied</param>
        /// <param name="time">Time, in milliseconds, for effect playing</param>
        public virtual void ShowAnimate(int x, int y, AnimateMode mode, uint time)
        {
            this._location.X = x;
            this._location.Y = y;
            this.ShowAnimate(mode, time);
        }
        /// <summary>
        /// Hides the window and release it's handle.
        /// </summary>
        public virtual void Hide()
        {
            if (base.Handle == IntPtr.Zero)
                return;
            User32.ShowWindow(base.Handle, User32.SW_HIDE);
            this.DestroyHandle();
        }
        /// <summary>
        /// Hides the window with animation effect and release it's handle.
        /// </summary>
        /// <param name="mode">Effect to be applied</param>
        /// <param name="time">Time, in milliseconds, for effect playing</param>
        public virtual void HideAnimate(AnimateMode mode, uint time)
        {
            if (base.Handle == IntPtr.Zero)
                return;
            uint dwFlag = 0;
            switch (mode)
            {
                case AnimateMode.Blend:
                    dwFlag = User32.AW_BLEND;
                    break;
                case AnimateMode.ExpandCollapse:
                    dwFlag = User32.AW_CENTER;
                    break;
                case AnimateMode.SlideLeftToRight:
                    dwFlag = User32.AW_HOR_POSITIVE | User32.AW_SLIDE;
                    break;
                case AnimateMode.SlideRightToLeft:
                    dwFlag = User32.AW_HOR_NEGATIVE | User32.AW_SLIDE;
                    break;
                case AnimateMode.SlideTopToBottom:
                    dwFlag = User32.AW_VER_POSITIVE | User32.AW_SLIDE;
                    break;
                case AnimateMode.SlideBottmToTop:
                    dwFlag = User32.AW_VER_NEGATIVE | User32.AW_SLIDE;
                    break;
                case AnimateMode.RollLeftToRight:
                    dwFlag = User32.AW_HOR_POSITIVE;
                    break;
                case AnimateMode.RollRightToLeft:
                    dwFlag = User32.AW_HOR_NEGATIVE;
                    break;
                case AnimateMode.RollBottmToTop:
                    dwFlag = User32.AW_VER_NEGATIVE;
                    break;
                case AnimateMode.RollTopToBottom:
                    dwFlag = User32.AW_VER_POSITIVE;
                    break;
            }
            dwFlag |= User32.AW_HIDE;
            if ((dwFlag & User32.AW_BLEND) != 0)
                this.AnimateWithBlend(false, time);
            else
                User32.AnimateWindow(base.Handle, time, dwFlag);
            this.Hide();
        }
        /// <summary>
        /// Close the window and destroy it's handle.
        /// </summary>
        public virtual void Close()
        {
            this.Hide();
            this.Dispose();
        }

        private void AnimateWithBlend(bool show, uint time)
        {
            byte originalAplha = this._alpha;
            byte p = (byte)(originalAplha / (time / 10));
            if (p == 0) p++;
            if (show)
            {
                this._alpha = 0;
                this.UpdateLayeredWindow();
                User32.ShowWindow(base.Handle, User32.SW_SHOWNOACTIVATE);
            }
            for (byte i = show ? (byte)0 : originalAplha; (show ? i <= originalAplha : i >= (byte)0); i += (byte)(p * (show ? 1 : -1)))
            {
                this._alpha = i;
                this.UpdateLayeredWindow();
                if ((show && i > originalAplha - p) || (!show && i < p))
                    break;
            }
            this._alpha = originalAplha;
            if (show)
                this.UpdateLayeredWindow();
        }

        private void CreateWindowOnly()
        {

            CreateParams params1 = new CreateParams();
            params1.Caption = "FloatingNativeWindow";
            int nX = this._location.X;
            int nY = this._location.Y;
            Screen screen1 = Screen.FromHandle(base.Handle);
            if ((nX + this._size.Width) > screen1.Bounds.Width)
            {
                nX = screen1.Bounds.Width - this._size.Width;
            }
            if ((nY + this._size.Height) > screen1.Bounds.Height)
            {
                nY = screen1.Bounds.Height - this._size.Height;
            }
            this._location = new Point(nX, nY);
            Size size1 = this._size;
            Point point1 = this._location;
            params1.X = nX;
            params1.Y = nY;
            params1.Height = size1.Height;
            params1.Width = size1.Width;
            params1.Parent = IntPtr.Zero;
            uint ui = User32.WS_POPUP;
            params1.Style = (int)ui;
            params1.ExStyle = User32.WS_EX_TOPMOST | User32.WS_EX_TOOLWINDOW | User32.WS_EX_LAYERED | User32.WS_EX_NOACTIVATE | User32.WS_EX_TRANSPARENT;
            this.CreateHandle(params1);
            this.UpdateLayeredWindow();
        }
        #endregion

        #region == Other messages ==
        private void PerformWmPaint_WmPrintClient(ref Message m, bool isPaintMessage)
        {
            PAINTSTRUCT paintstruct1;
            RECT rect1;
            Rectangle rectangle1;
            paintstruct1 = new PAINTSTRUCT();
            IntPtr ptr1 = (isPaintMessage ? User32.BeginPaint(m.HWnd, ref paintstruct1) : m.WParam);
            rect1 = new RECT();
            User32.GetWindowRect(base.Handle, ref rect1);
            rectangle1 = new Rectangle(0, 0, rect1.Right - rect1.Left, rect1.Bottom - rect1.Top);
            using (Graphics graphics1 = Graphics.FromHdc(ptr1))
            {
                Bitmap bitmap1 = new Bitmap(rectangle1.Width, rectangle1.Height);
                using (Graphics graphics2 = Graphics.FromImage(bitmap1))
                    this.PerformPaint(new PaintEventArgs(graphics2, rectangle1));
                graphics1.DrawImageUnscaled(bitmap1, 0, 0);
            }
            if (isPaintMessage)
                User32.EndPaint(m.HWnd, ref paintstruct1);
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == 15) // WM_PAINT
            {
                this.PerformWmPaint_WmPrintClient(ref m, true);
                return;
            }
            else if (m.Msg == 0x318) // WM_PRINTCLIENT
            {
                this.PerformWmPaint_WmPrintClient(ref m, false);
                return;
            }
            base.WndProc(ref m);
        }
        #endregion

        #region == Size and Location ==
        protected virtual void SetBoundsCore(int x, int y, int width, int height)
        {
            if (((this.X != x) || (this.Y != y)) || ((this.Width != width) || (this.Height != height)))
            {
                if (base.Handle != IntPtr.Zero)
                {
                    int num1 = 20;
                    if ((this.X == x) && (this.Y == y))
                    {
                        num1 |= 2;
                    }
                    if ((this.Width == width) && (this.Height == height))
                    {
                        num1 |= 1;
                    }
                    User32.SetWindowPos(base.Handle, IntPtr.Zero, x, y, width, height, (uint)num1);
                }
                else
                {
                    this.Location = new Point(x, y);
                    this.Size = new Size(width, height);
                }
            }
        }
        #endregion

        #endregion

        #region #  Properties  #
        /// <summary>
        /// Get or set position of top-left corner of floating native window in screen coordinates
        /// </summary>
        public virtual Point Location
        {
            get { return this._location; }
            set
            {
                if (base.Handle != IntPtr.Zero)
                {
                    this.SetBoundsCore(value.X, value.Y, this._size.Width, this._size.Height);
                    RECT rect = new RECT();
                    User32.GetWindowRect(base.Handle, ref rect);
                    this._location = new Point(rect.Left, rect.Top);
                    this.UpdateLayeredWindow();
                }
                else
                {
                    this._location = value;
                }
            }
        }
        /// <summary>
        /// Get or set size of client area of floating native window
        /// </summary>
        public virtual Size Size
        {
            get { return this._size; }
            set
            {
                if (base.Handle != IntPtr.Zero)
                {
                    this.SetBoundsCore(this._location.X, this._location.Y, value.Width, value.Height);
                    RECT rect = new RECT();
                    User32.GetWindowRect(base.Handle, ref rect);
                    this._size = new Size(rect.Right - rect.Left, rect.Bottom - rect.Top);
                    this.UpdateLayeredWindow();
                }
                else
                {
                    this._size = value;
                }
            }
        }
        /// <summary>
        /// Gets or sets the height of the floating native window
        /// </summary>
        public int Height
        {
            get { return this._size.Height; }
            set
            {
                this._size = new Size(this._size.Width, value);
            }
        }
        /// <summary>
        /// Gets or sets the width of the floating native window
        /// </summary>
        public int Width
        {
            get { return this._size.Width; }
            set
            {
                this._size = new Size(value, this._size.Height);
            }
        }
        /// <summary>
        /// Get or set x-coordinate of top-left corner of floating native window in screen coordinates
        /// </summary>
        public int X
        {
            get { return this._location.X; }
            set
            {
                this.Location = new Point(value, this.Location.Y);
            }
        }
        /// <summary>
        /// Get or set y-coordinate of top-left corner of floating native window in screen coordinates
        /// </summary>
        public int Y
        {
            get { return this._location.Y; }
            set
            {
                this.Location = new Point(this.Location.X, value);
            }
        }
        /// <summary>
        /// Get rectangle represented client area of floating native window in client coordinates(top-left corner always has coord. 0,0)
        /// </summary>
        public Rectangle Bound
        {
            get
            {
                return new Rectangle(new Point(0, 0), this._size);
            }
        }
        /// <summary>
        /// Get or set full opacity(255) or full transparency(0) or any intermediate state for floating native window transparency
        /// </summary>
        public byte Alpha
        {
            get { return this._alpha; }
            set
            {
                if (this._alpha == value) return;
                this._alpha = value;
                this.UpdateLayeredWindow();
            }
        }
        #endregion

        #region IDisposable Members
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
        private void Dispose(bool disposing)
        {
            if (!this._disposed)
            {
                this.DestroyHandle();
                this._disposed = true;
            }
        }
        #endregion
    }
}
